#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h> // file option
#include <dirent.h>
#include <pthread.h>
// #include <iostream>
// #include <sstream>
// #include <vector>
// #include <list>
// #include <map>
// #include <thread>
// #include <string>
// #include <iomanip>
// #include <fstream>
// #include <vector>
// #include <set>
// #include <algorithm>
// #include <unordered_map>
// #include <mutex>
// #include <atomic>
// #include <bits/wordsize.h>
// #include <bits/timesize.h>
#include <linux/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
// using namespace std;
// 这里有select使用，epoll和poll自行了解。select可以用来管理文件描述符(文件，设备文件，sock等)，范围1024，poll没有这个限制。
// tcp连接3次握手，4次挥手。2分钟等待期。包括(主动释放)closewait，(被动释放)timewait。
// 服务器包括：sock，bind，listen，accept，send，read，close。
// 客服端包括：sock，(bind如果要指定ip和端口需要用到)，connect，send，read，close。
int runflag = 1;
struct fdMngInfoSturct
{
    int fd;
    struct sockaddr_in cntaddr;
    int okflag; // 0:wait,1:ok,2:err need free
    char data[1024];
    int datalen;
};

static int selectCom(struct fdMngInfoSturct *fdList, int fdLen, time_t toutms)
{
    fd_set rset;
    struct timeval tv;
    int maxSockfd = 0;
    int rc = 0;
    if (fdLen > 100)
    {
        printf("fdlen err : %d\n", fdLen);
        return -1;
    }
    if (!fdList)
    {
        printf("fdlist err : %d\n", fdLen);
        return -1;
    }
    tv.tv_sec = toutms / 1000;
    tv.tv_usec = toutms % 1000 * 1000;
    FD_ZERO(&rset);
    for (int i = 0; i < fdLen; i++)
    {
        fdList[i].okflag = 0;
        if (fdList[i].fd > 0)
        {
            FD_SET(fdList[i].fd, &rset);
            if (maxSockfd < fdList[i].fd + 1)
            {
                maxSockfd = fdList[i].fd + 1;
            }
        }
    }
    rc = select(maxSockfd, &rset, NULL, NULL, &tv);
    if (rc < 0)
    {
        printf("select err : %d\n", rc);
        return -1;
    }
    else if (rc == 0)
    {
        return -2;
    }
    for (int i = 0; i < fdLen; i++)
    {
        if (FD_ISSET(fdList[i].fd, &rset))
        {
            fdList[i].datalen = read(fdList[i].fd, fdList[i].data, sizeof(fdList[i].data));
            if (fdList[i].datalen <= 0)
            {
                fdList[i].okflag = 2;
            }
            else
            {
                fdList[i].okflag = 1;
            }
        }
        else
        {
            fdList[i].okflag = 0;
        }
    }
    return 0;
}

void *clientFunc(void *usrdata)
{
    fd_set rset;
    struct timeval tv;
    int rc = 0;
    int cliSockFd = -1;
    struct sockaddr_in srvAddr;
    socklen_t tmp_addr_len;
    char tmpbuf[1024];
    struct ifreq binddevice;

    strncpy(binddevice.ifr_name, "eth0 ", strlen("ens33") + 1);
    cliSockFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    // 指定网卡
    //  setsockopt(cliSockFd, SOL_SOCKET, SO_BINDTODEVICE, (char*)&binddevice, sizeof(binddevice));
    if (cliSockFd < 0)
    {
        printf("cli scok err\n");
        return NULL;
    }
    sleep(1); // wait srv up
    // 配置服务器IP
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(9754);
    srvAddr.sin_addr.s_addr = inet_addr("192.168.6.48");
    printf("begin connect\n");
    // 连接
    if (connect(cliSockFd, (const struct sockaddr *)&srvAddr, sizeof(srvAddr)) < 0)
    {
        printf("connect err:%d\n", errno);
        goto finishCli;
    }
    // 连接成功发送数据
    send(cliSockFd, "cli connected, holle", strlen("cli connected, holle"), 0);

    while (runflag)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 10000;
        FD_ZERO(&rset);
        FD_SET(cliSockFd, &rset);
        // 等待接收。
        rc = select(cliSockFd + 1, &rset, NULL, NULL, &tv);
        if (rc < 0)
        {
            printf("cli select err : %d\n", rc);
            goto finishCli;
        }
        else if (rc > 0)
        {
            if (FD_ISSET(cliSockFd, &rset))
            {
                // 获取成功
                int tmplen = read(cliSockFd, tmpbuf, sizeof(tmpbuf));
                if (tmplen < 0)
                {
                    printf("cli disconnect err : %d\n", rc);
                    goto finishCli;
                }
                else
                {
                    // 通过文件描述符获得服务器ip。
                    getpeername(cliSockFd, (struct sockaddr *)&srvAddr, &tmp_addr_len);
                    printf("cnt rcv and exit[%s:%d]%s\n", inet_ntoa(srvAddr.sin_addr), ntohs(srvAddr.sin_port),
                           tmpbuf);
                    goto finishCli;
                }
            }
        }
        sleep(1);
    }
finishCli:
    printf("finish cli scok\n");
    sleep(5);
    runflag = 0;
    if (cliSockFd > 0)
    {
        close(cliSockFd);
    }
    return NULL;
}

#define CONNECT_MAX_LEN 10
void *serverFunc(void *usrdata)
{
    fd_set rset;
    struct timeval tv;
    int rc = 0;
    int i;
    int srvSockFd = -1;
    struct sockaddr_in srvAddr;
    int bReuseaddr = 1;
    struct fdMngInfoSturct conFdBuf[CONNECT_MAX_LEN];
    memset(conFdBuf, 0, sizeof(conFdBuf));

    srvSockFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (srvSockFd < 0)
    {
        printf("srv scok err\n");
        return NULL;
    }
    setsockopt(srvSockFd, SOL_SOCKET, SO_REUSEADDR, (const char *)&bReuseaddr, sizeof(bReuseaddr));
    bzero(&srvAddr, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_port = htons(9754);
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 绑定自己IP和端口。INADDR_ANY表示所有本地IP，因为我们可能有以太网卡，wifi，内部loop。他们的ip都不一样。
    if (bind(srvSockFd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) == -1)
    {
        printf("binde err:%d\n", errno);
        goto finishSrv;
    }
    // 监听。5表示握手过程同时支持几个，不是多少个客户端！
    if (listen(srvSockFd, 5) < 0)
    {
        printf("binde err\n");
        goto finishSrv;
    }
    printf("srv ok!\n");
    while (runflag)
    {
        tv.tv_sec = 0;
        tv.tv_usec = 10000;
        FD_ZERO(&rset);
        FD_SET(srvSockFd, &rset);
        rc = select(srvSockFd + 1, &rset, NULL, NULL, &tv);
        // select返回如果<0表示错误，用不了了。=0表示等待超时了，>0表示有多个文件描述符有数据，可以通讯了。
        if (rc < 0)
        {
            printf("select err : %d\n", rc);
            goto finishSrv;
        }
        else if (rc > 0)
        {
            if (FD_ISSET(srvSockFd, &rset))
            {
                // 监听成功，通过accept完成连接，得到与客服端通讯的文件描述符-tmpclifd。
                struct sockaddr_in tmpcliaddr;
                socklen_t clie_addr_len;
                int tmpclifd = accept(srvSockFd, (struct sockaddr *)&tmpcliaddr, &clie_addr_len);
                getpeername(tmpclifd, (struct sockaddr *)&tmpcliaddr, &clie_addr_len);
                printf("accept[%d][%s:%d]\n", tmpclifd, inet_ntoa(tmpcliaddr.sin_addr), ntohs(tmpcliaddr.sin_port));
                for (i = 0; i < CONNECT_MAX_LEN; i++)
                {
                    if (conFdBuf[i].fd <= 0)
                    {
                        conFdBuf[i].fd = tmpclifd;
                        conFdBuf[i].cntaddr = tmpcliaddr;
                        break;
                    }
                }
            }
        }
        // 处理客户端发送的消息，并回传。
        selectCom(conFdBuf, CONNECT_MAX_LEN, 40);
        {
            for (i = 0; i < CONNECT_MAX_LEN; i++)
            {
                if (conFdBuf[i].okflag == 2)
                {
                    printf("free %s at port %d\n", inet_ntoa(conFdBuf[i].cntaddr.sin_addr), ntohs(conFdBuf[i].cntaddr.sin_port)); // 打印该客户端的IP地址和端口号
                    if (conFdBuf[i].fd > 0)
                    {
                        close(conFdBuf[i].fd);
                    }
                    memset(&conFdBuf[i], 0, sizeof(conFdBuf[i]));
                }
                else if (conFdBuf[i].okflag == 1)
                {
                    printf("cnt rcv[%s:%d]%s\n", inet_ntoa(conFdBuf[i].cntaddr.sin_addr), ntohs(conFdBuf[i].cntaddr.sin_port),
                           conFdBuf[i].data);
                    send(conFdBuf[i].fd, conFdBuf[i].data, strlen(conFdBuf[i].data), 0);
                }
            }
        }
    }
finishSrv:
    printf("finish srv scok\n");
    runflag = 0;
    if (srvSockFd > 0)
    {
        close(srvSockFd);
    }
    for (i = 0; i < CONNECT_MAX_LEN; i++)
    {
        if (conFdBuf[i].fd > 0)
        {
            close(conFdBuf[i].fd);
        }
    }
    return NULL;
}

int main()
{
    int tmpflag;
    pthread_t cliThreadFd = -1;
    pthread_t srvThreadFd = -1;
    tmpflag = pthread_create(&cliThreadFd, NULL, clientFunc, NULL);
    // printf("client flag = %d!\n", tmpflag);
    if (tmpflag < 0)
    {
        printf("client thread err flag = %d!\n", tmpflag);
        goto finishPoint;
    }
    tmpflag = pthread_create(&srvThreadFd, NULL, serverFunc, NULL);
    if (tmpflag < 0)
    {
        printf("srv thread err flag = %d!\n", tmpflag);
        goto finishPoint;
    }
    while (runflag)
    {
        usleep(10000);
    }
    printf("hello world!\n");
finishPoint:
    runflag = 0;
    if (cliThreadFd > 0)
    {
        pthread_join(cliThreadFd, NULL);
    }
    if (srvThreadFd > 0)
    {
        pthread_join(srvThreadFd, NULL);
    }
    return 0;
}

// 正常运行结果：
//  srv ok!
//  begin connect
//  accept[5][192.168.6.48:46854]
//  cnt rcv[192.168.6.48:46854]cli connected, holle
//  cnt rcv and exit[192.168.6.48:9754]cli connected, holle
//  finish cli scok
//  hello world!
//  free 192.168.6.48 at port 46854
//  finish srv scok
